package com.wolfking.aggregate;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;


/**
 * 并发聚合的动态代理的工厂类
 * 经过隔壁老王处理的JDK动态代理的工厂类
 * spring的加载路径，先调用ApplicationContextAware的setApplicationContext,
 * 再调用InitializingBean的afterPropertiesSet，根据Type解析bean
 * FactoryBean 是注入的时候，调用getObject
 *
 * @author 赵伟伟(wolfking)
 * Created on 2019/7/15 14:48
 */
@Slf4j
class ConcurrentAggregateProxyFactory<T> implements FactoryBean<T>, InitializingBean, ApplicationContextAware {

    /*通过构造方法注入设置*/
    private final Class<T> proxyClass;
    /*spring的上下文*/
    private ApplicationContext applicationContext;
    /*方法的并发控制*/
    private final Map<Method, AggregateMethodConfig> methodConfig = Maps.newHashMap();
    /*方法参数和下标的组合*/
    private final Map<Method, Map<String, Integer>> methodParameterConfig = Maps.newHashMap();

    /*方法是否忽略并发异常*/
    private final Map<Method, Boolean> ignoreExceptionConfig = Maps.newHashMap();

    private final Map<Method, ExecutorService> executorServiceConfig = Maps.newHashMap();

    @Autowired(required = false)
    @Qualifier(AgggregateStatic.THREAD_POOL_NAME)
    private ExecutorService executorService;

    ConcurrentAggregateProxyFactory(Class<T> proxyClass) {
        this.proxyClass = proxyClass;
    }

    @Override
    @SuppressWarnings("all")
    public T getObject() throws Exception {
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{proxyClass},
                new ConcurrentAggregateProxy(methodConfig, executorServiceConfig, methodParameterConfig, ignoreExceptionConfig));
    }

    @Override
    public Class<?> getObjectType() {
        return proxyClass;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void afterPropertiesSet() {
        if (this.executorService == null) {
            this.executorService = AgggregateStatic.executorService;
        }
        ConcurrentAggregate concurrentAggregate = AnnotatedElementUtils.findMergedAnnotation(proxyClass, ConcurrentAggregate.class);

        AggregateThreadPool aggregateThreadPool = concurrentAggregate.value();
        final String name = aggregateThreadPool.name();
        if (StringUtils.isNotBlank(name)) {
            this.executorService = AgggregateStatic.getConfigExecutorService(applicationContext, aggregateThreadPool);
        }

        @SuppressWarnings("unchecked")
        Set<Method> methods = ReflectionUtils.getAllMethods(proxyClass, ReflectionUtils.withAnnotation(AggregateProviders.class));
        for (Method method : methods) {
            Map<String, Integer> parameterNameIndexMap = Maps.newHashMap();
            Parameter[] parameterArray = method.getParameters();
            if (parameterArray != null && parameterArray.length > 0) {
                for (int i = 0; i < parameterArray.length; i++) {
                    Parameter parameter = parameterArray[i];
                    AggregateParameterName annotation = parameter.getAnnotation(AggregateParameterName.class);
                    if (annotation != null) {
                        if (parameterNameIndexMap.containsKey(annotation.value())) {
                            throw new AggregateException(String.format("Multiple parameter is %s", annotation.value()));
                        }
                        parameterNameIndexMap.put(annotation.value(), i);
                    } else {
                        log.info("current method {} has no used parameter index is {}", method.getName(), i);
                    }
                }
            }
            AggregateProviders aggregateProviders = method.getAnnotation(AggregateProviders.class);
            AggregateThreadPool currentMethodThreadPool = aggregateProviders.threadPool();
            if (StringUtils.isNotBlank(currentMethodThreadPool.name())) {
                executorServiceConfig.put(method, AgggregateStatic.getConfigExecutorService(applicationContext, currentMethodThreadPool));
            } else {
                executorServiceConfig.put(method, this.executorService);
            }
            AggregateProvider[] providers = aggregateProviders.value();
            if (providers.length <= 1) {
                throw new AggregateException(String.format("%s#%s provider error", proxyClass.getName(), method.getName()));
            }
            List<AggregateProviderConfig> providerConfigList = Lists.newArrayList();
            for (AggregateProvider aggregateProvider : providers) {
                Object bean;
                Method providerMethod = null;
                if (!StringUtils.isEmpty(aggregateProvider.beanName())) {
                    bean = applicationContext.getBean(aggregateProvider.beanName());
                    if (bean == null) {
                        throw new NoSuchBeanDefinitionException(aggregateProvider.beanName());
                    }
                } else if (!aggregateProvider.beanClass().equals(Void.class)) {
                    bean = applicationContext.getBean(aggregateProvider.beanClass());
                    if (bean == null) {
                        throw new NoSuchBeanDefinitionException(aggregateProvider.beanClass().getName());
                    }
                } else {
                    throw new AggregateException(String.format("%s#%s provider error", proxyClass.getName(), method.getName()));
                }
                List<String> parametersName = Lists.newArrayList();
                AggregateParameter[] parameters = aggregateProvider.parameters();
                for (AggregateParameter parameter : parameters) {
                    if (parameterNameIndexMap.containsKey(parameter.value())) {
                        parametersName.add(parameter.value());
                    } else {
                        throw new AggregateException(String.format("parameter %s is missing", parameter.value()));
                    }
                }
                /*解析方法的后续待优化,还得匹配参数类型*/
                @SuppressWarnings("unchecked")
                Set<Method> methodSet = ReflectionUtils.getMethods(bean.getClass(), ReflectionUtils.withName(aggregateProvider.method()));
                Iterator<Method> iterator = methodSet.iterator();
                if (methodSet.size() == 0) {
                    throw new AggregateException(String.format("%s#%s provider method error",
                            StringUtils.isEmpty(aggregateProvider.beanName()) ? aggregateProvider.beanClass().getName() : aggregateProvider.beanName(), aggregateProvider.method()));
                } else if (methodSet.size() > 1) {
                    while (iterator.hasNext()) {
                        Method next = iterator.next();
                        if (next.getParameterCount() == parametersName.size()) {
                            providerMethod = next;
                            break;
                        }
                    }
                    if (providerMethod == null) {
                        throw new AggregateException(String.format("%s#%s provider no suitable method error",
                                StringUtils.isEmpty(aggregateProvider.beanName()) ? aggregateProvider.beanClass().getName() : aggregateProvider.beanName(), aggregateProvider.method()));
                    }
                } else {
                    providerMethod = iterator.next();
                }
                int size = parametersName.size();
                AggregateProviderConfig providerConfig = AggregateProviderConfig.builder().bean(bean).method(providerMethod).discardResult(aggregateProvider.discardResult())
                        .level(aggregateProvider.level()).parameterNames(parametersName.toArray(new String[size])).ignoreException(aggregateProvider.ignoreException()).build();
                providerConfigList.add(providerConfig);
            }
            AggregateConsumerConfig aggregateConsumerConfig = null;
            AggregateConsumer aggregateConsumer = method.getAnnotation(AggregateConsumer.class);
            if (aggregateConsumer != null) {
                Object bean;
                Method consumeMethod;
                if (!StringUtils.isEmpty(aggregateConsumer.beanName())) {
                    bean = applicationContext.getBean(aggregateConsumer.beanName());
                    if (bean == null) {
                        throw new NoSuchBeanDefinitionException(aggregateConsumer.beanName());
                    }
                } else if (!aggregateConsumer.beanClass().equals(Void.class)) {
                    bean = applicationContext.getBean(aggregateConsumer.beanClass());
                    if (bean == null) {
                        throw new NoSuchBeanDefinitionException(aggregateConsumer.beanClass().getName());
                    }
                } else {
                    throw new AggregateException(String.format("%s#%s consumer error", proxyClass.getName(), method.getName()));
                }
                /*解析方法的后续待优化,还得匹配参数类型*/
                @SuppressWarnings("unchecked")
                Set<Method> methodSet = ReflectionUtils.getMethods(bean.getClass(), ReflectionUtils.withName(aggregateConsumer.method()));
                if (methodSet.size() == 0) {
                    throw new AggregateException(String.format("%s#%s consumer method error",
                            StringUtils.isEmpty(aggregateConsumer.beanName()) ? aggregateConsumer.beanClass().getName() : aggregateConsumer.beanName(), aggregateConsumer.method()));
                } else if (methodSet.size() > 1) {
                    throw new AggregateException(String.format("%s#%s consumer no suitable method error",
                            StringUtils.isEmpty(aggregateConsumer.beanName()) ? aggregateConsumer.beanClass().getName() : aggregateConsumer.beanName(), aggregateConsumer.method()));
                } else {
                    consumeMethod = methodSet.iterator().next();
                }
                aggregateConsumerConfig = new AggregateConsumerConfig(bean, consumeMethod, aggregateConsumer.discardResult());
            }
            method.setAccessible(true);
            methodConfig.put(method, new AggregateMethodConfig(providerConfigList, aggregateConsumerConfig, aggregateProviders.timeout()));
            methodParameterConfig.put(method, parameterNameIndexMap);
            ignoreExceptionConfig.put(method, aggregateProviders.ignoreException());
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
